package se.perp.c4.control.bot;

import java.util.Map;
import se.perp.c4.control.GameController;
import se.perp.c4.model.Actor;
import se.perp.c4.model.Actor.EActor;
import se.perp.c4.model.Actor.Side;
import se.perp.c4.model.ActorCollection;
import se.perp.c4.model.Move;
import se.perp.c4.model.Territory.ETerritory;
import se.perp.c4.model.TerritoryCollection;
import se.perp.c4.net.Message;
import static se.perp.c4.model.Actor.Side.*;
import static se.perp.c4.control.GameController.WaitState.*;

abstract class Bot implements IBot, Runnable {

    private boolean isRunning = false;
    protected ActorCollection actorColl = new ActorCollection();
    protected TerritoryCollection terrColl = new TerritoryCollection();
    protected Side botSide = null;
    protected final IBotSideConnection conn;
    private GameController.WaitState waitState = PLACEMENT;

    public Bot(IBotSideConnection conn) {
        this.conn = conn;

        terrColl.emptyAll();
        actorColl.setAllAsUnplaced();
        actorColl.setAllAsLiving();


        Thread messageLoop = new Thread(this);
        messageLoop.start();
    }

    /** Creates a new move, decided by the current state of the bot's
     * internal data.
     * @return an instance of Move.
     */
    protected abstract Move createMove();

    /**
     * Creates an initial placement map. How to do this is up to the individual
     * bot class.
     * @return an initial placement.
     */
    protected abstract Map<EActor, ETerritory> createPlacement();

    /**
     * Decides what the bot should do upon receiving a message of type MOVE.
     * @param m 
     */
    protected abstract void handleMsg_battleMove(Message m);

    private void handleMsg_move(Message m) {
        if (m.getType() == Message.Type.BATTLE_MOVE) {
            handleMsg_battleMove(m);
        } else if (m.getType() == Message.Type.MOVE) {
            Move move = (Move)m.getPayload();
            Actor a = actorColl.get(move.attacker);
            terrColl.moveActor(a, move.to);
            sendMove();
        }
    }

    private void handleMessage(Message m) {
        System.out.println("[BOT] should handle message of type: " + m.getType());

        switch (waitState) {
            case PLACEMENT:
                if (m.getType() == Message.Type.SIDE) {
                    botSide = (Side) m.getPayload();
                } else if (m.getType() == Message.Type.PLACEMENT) {
                    sendPlacement();
                    waitState = MOVE;
                    if (botSide == DARK) {
                        sendMove();
                    }
                }
                break;
            case MOVE:
                handleMsg_move(m);
                break;
            case CARD:
                break;
            case I_GANDALF_WAITING_FOR_CARD:
                break;
            case ATTACKER_RETREAT_MOVE:
                break;
            case DEFENDER_RETREAT_MOVE:
                break;
            case SIMULTANEOUS_RETREAT_MOVE:
                break;
            case SARUMAN_DECISION:
                break;
            case MAGIC_MAGIC_CARD_WAITING:
                break;
            case PIPPIN_DECISION:
                break;
            case SAM_DECISION:
                break;
            case FRODO_DECISION: {
            }
                break;
            default:
                break;
        }

    }

    /** Creates and sends a move to the player via the BotSideConnection. */
    protected void sendMove() {
        Move move = createMove();
        terrColl.moveActor(actorColl.get(move.attacker), move.to);
        boolean isBattleMove = (move.defender != null);
        Message m;
        if (isBattleMove) {
            m = Message.newBattleMove(move);
        } else {
            m = Message.newMove(move);
        }
        sendToPlayer(m);
    }

    /** Creates and sends a placement to the player via the BotSideConnection. */
    private void sendPlacement() {
        Map<EActor, ETerritory> placement = createPlacement();
        Message m = Message.newPlacement(placement);

        System.out.println("[BOT] Sending placement:\n\t" + placement);
        sendToPlayer(m);
        for (EActor ea : placement.keySet()) {
            ETerritory et = placement.get(ea);
            terrColl.get(et).addActor(actorColl.get(ea));
        }
    }

    private void sendToPlayer(Message m) {
        System.out.println("[BOT] Sending message of type " + m.getType() + " to the connection.");
        conn.sendToPlayer(m);
    }

    /** The bot thread's message loop. Is started automatically upon instantiation. */
    public void run() {
        System.out.println("[BOT] Message loop started.");
        isRunning = true;
        while (isRunning) {
            System.out.println("[BOT] Waiting...");
            Message m = conn.waitForPlayerMessage();
            handleMessage(m);
        }
        isRunning = false;
    }

    /** Shuts down the bot's message loop. */
    public void kill() {
        isRunning = false;
    }
}
